package com.eksframework.commons.core.converter;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Objects;

/**
 * Long转换器
 *
 * @author: chenfenghai
 * @version: 1.0
 * @date: 2020-04-24 9:17
 * @blame Team
 */
public class LongConverter<T> extends AbstractNumberConverter<T> {

    /**
     * 默认值
     */
    private static long longDefaultValue;
    /**
     * Long对象
     */
    private Long longObject;

    /**
     * Constructs an instance with the value present.
     *
     * @param object convert value
     */
    protected LongConverter(T object) {
        super(object);
    }

    /**
     * * 创建实例
     *
     * @param value 输入参数
     * @return not null
     */
    public static <T> LongConverter of(T value) {
        return new LongConverter<>(value);
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * get rounding mode to apply
     *
     * @param roundingMode rounding mode
     * @return RoundingMode
     * @see RoundingMode
     */
    @Override
    RoundingMode getRoundingMode(RoundingMode roundingMode) {
        return roundingMode == null ? RoundingMode.DOWN : roundingMode;
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>Long</code>
     *
     * @param value        {@code Object} to convert to a {@code Long}.
     * @param roundingMode The rounding mode to apply
     * @return Long
     * @throws NullPointerException  – if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code Long}.
     */
    private Long convert(final T value, final RoundingMode roundingMode) {
        Objects.requireNonNull(value);
        if(longObject != null){
            return longObject;
        }else if (Long.class.equals(value.getClass())) {
            this.longObject = (Long) value;
        } else if (Boolean.class.equals(value.getClass())) {
            this.longObject = Boolean.parseBoolean(value.toString()) ? 1L : 0L;
        } else {
            final BigDecimal bigDecimal = BigDecimalConverter.of(super.object)
                    .toBigDecimalThrow(0, getRoundingMode(roundingMode));
            if(bigDecimal.compareTo(new BigDecimal(Long.MAX_VALUE)) > 0){
                this.longObject = Long.MAX_VALUE;
            }else if(bigDecimal.compareTo(new BigDecimal(Long.MIN_VALUE)) < 0){
                this.longObject = Long.MIN_VALUE;
            }else{
                this.longObject = bigDecimal.longValue();
            }
        }
        return longObject;
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>Long</code>, returning
     * <code>ZERO</code> if the conversion fails.</p>
     *
     * @return a {@code long}, not null
     * @see #longValue(RoundingMode)
     */
    public long longValue() {
        return longValue(null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>long</code>, returning
     * <code>ZERO</code> if the conversion fails.</p>
     *
     * @param roundingMode The rounding mode to apply
     * @return a {@code long}, not null
     * @see #longValueDefault(long, RoundingMode)
     */
    public long longValue(@Nullable final RoundingMode roundingMode) {
        return Objects.requireNonNull(toLongDefault(longDefaultValue, roundingMode));
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>long</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @return a {@code long}, may be null
     * @see #longValueDefault(long, RoundingMode)
     */
    public long longValueDefault(final long defaultValue) {
        return longValueDefault(defaultValue, null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>long</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @param roundingMode The rounding mode to apply
     * @return a {@code long}, may be null
     * @see #convert(T, RoundingMode)
     */
    public long longValueDefault(final long defaultValue, @Nullable final RoundingMode roundingMode) {
        try {
            return super.object == null ? defaultValue : convert(super.object, roundingMode);
        } catch (Exception ex) {
            return defaultValue;
        }
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>long</code>
     *
     * @return a {@code long}, not null
     * @throws NullPointerException  if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code long}.
     * @see #convert(T, RoundingMode)
     */
    public long longValueThrow() {
        return convert(super.object, null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>long</code>
     *
     * @param roundingMode The rounding mode to apply
     * @return a {@code long}, not null
     * @throws NullPointerException  if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code long}.
     * @see #convert(T, RoundingMode)
     */
    public long longValueThrow(@Nullable final RoundingMode roundingMode) {
        return convert(super.object, roundingMode);
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>Long</code>, returning
     * <code>ZERO</code> if the conversion fails.</p>
     *
     * @return a {@code Long}, not null
     * @see #toLong(RoundingMode)
     */
    @Nonnull
    public Long toLong() {
        return toLong(null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>Long</code>, returning
     * <code>ZERO</code> if the conversion fails.</p>
     *
     * @param roundingMode The rounding mode to apply
     * @return a {@code Long}, not null
     * @see #toLongDefault(Long, RoundingMode)
     */
    @Nonnull
    public Long toLong(@Nullable final RoundingMode roundingMode) {
        return Objects.requireNonNull(toLongDefault(longDefaultValue, roundingMode));
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>Long</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @return a {@code Long}, may be null
     * @see #toLongDefault(Long, RoundingMode)
     */
    @Nullable
    public Long toLongDefault(@Nullable final Long defaultValue) {
        return toLongDefault(defaultValue, null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>Long</code>, returning a
     * default value if the conversion fails.</p>
     *
     * @param defaultValue the default value
     * @param roundingMode The rounding mode to apply
     * @return a {@code Long}, may be null
     * @see #convert(T, RoundingMode)
     */
    @Nullable
    public Long toLongDefault(@Nullable final Long defaultValue, @Nullable final RoundingMode roundingMode) {
        try {
            return super.object == null ? defaultValue : convert(super.object, roundingMode);
        } catch (Exception ex) {
            return defaultValue;
        }
    }

    //-----------------------------------------------------------------------------------------------------------------

    /**
     * <p>Convert a <code>Object</code> to a <code>Long</code>
     *
     * @return a {@code Long}, not null
     * @throws NullPointerException  if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code BigDecimal}.
     * @see #convert(T, RoundingMode)
     */
    @Nonnull
    public Long toLongThrow() {
        return convert(super.object, null);
    }

    /**
     * <p>Convert a <code>Object</code> to a <code>Long</code>
     *
     * @param roundingMode The rounding mode to apply
     * @return a {@code Long}, not null
     * @throws NullPointerException  if the value is null.
     * @throws NumberFormatException if the value is not a valid representation of a {@code BigDecimal}.
     * @see #convert(T, RoundingMode)
     */
    @Nonnull
    public Long toLongThrow(@Nullable final RoundingMode roundingMode) {
        return convert(super.object, roundingMode);
    }
}
